package dao

import (
	"fmt"
	"reflect"

	"gitee.com/mkwchecking/sr-core/domain/dao/scope"
	"gorm.io/gorm"
)

var _ IDao[any] = (*internalDao[any])(nil)

func NewDao[Ent any](db *gorm.DB, opts ...Options) IDao[Ent] {
	opt := defaultOption()
	for _, o := range opts {
		o(opt)
	}

	return &internalDao[Ent]{
		db:  db,
		opt: *opt,
	}
}

type internalDao[Ent any] struct {
	db  *gorm.DB
	opt option
}

// Count implements IDao.
func (i *internalDao[Ent]) Count(cds ...any) (int64, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return 0, err
	}

	total := int64(0)
	err = db.Count(&total).Error
	return total, err
}

// Create implements IDao.
func (i *internalDao[Ent]) Create(ent *Ent, cds ...any) (*Ent, error) {
	model := new(Ent)
	db, err := i.makeScopes(i.db.Model(model), cds...)
	if err != nil {
		return nil, err
	}

	if userId, ok := db.Get(scope.OperatorKey); ok {
		if db.Statement != nil {
			db.Statement.SetColumn("CreatedBy", userId)
		}
	}

	err = db.Create(ent).Error
	return ent, err
}

// CreateMulti implements IDao.
func (i *internalDao[Ent]) CreateMulti(ents []*Ent, cds ...any) (int64, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return 0, err
	}

	if userId, ok := db.Get(scope.OperatorKey); ok {
		if db.Statement != nil {
			db.Statement.SetColumn("CreatedBy", userId)
		}
	}

	db = db.CreateInBatches(ents, len(ents))
	return db.RowsAffected, db.Error
}

// FindMulti implements IDao.
func (i *internalDao[Ent]) FindMulti(cds ...any) ([]*Ent, int64, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return nil, 0, err
	}

	ents := make([]*Ent, 0)
	total := int64(0)
	err = db.Find(&ents).
		Limit(-1).Offset(-1).
		Count(&total).
		Error

	return ents, int64(len(ents)), err
}

// FindOne implements IDao.
func (i *internalDao[Ent]) FindOne(cds ...any) (*Ent, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return nil, err
	}

	ent := new(Ent)
	err = db.First(ent).Error
	return ent, err
}

// ForceDelete implements IDao.
func (i *internalDao[Ent]) ForceDelete(cds ...any) error {
	model := new(Ent)
	db, err := i.makeScopes(i.db.Model(model), cds...)
	if err != nil {
		return err
	}

	if userId, ok := db.Get(scope.OperatorKey); ok {
		if db.Statement != nil {
			db.Statement.SetColumn("DeletedBy", userId)
		}
	}

	return db.Unscoped().Delete(model).Error
}

// SoftDelete implements IDao.
func (i *internalDao[Ent]) SoftDelete(cds ...any) error {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return err
	}

	if userId, ok := db.Get(scope.OperatorKey); ok {
		if db.Statement != nil {
			db.Statement.SetColumn("DeletedBy", userId)
		}
	}
	model := new(Ent)
	return db.Delete(model).Error
}

// Update implements IDao.
func (i *internalDao[Ent]) Update(ent *Ent, cds ...any) (*Ent, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return nil, err
	}

	if userId, ok := db.Get(scope.OperatorKey); ok {
		if db.Statement != nil {
			db.Statement.SetColumn("UpdatedBy", userId)
		}
	}
	err = db.Updates(ent).Error
	return ent, err
}

// UpdateColumns implements IDao.
func (i *internalDao[Ent]) UpdateColumns(cols []any, cds ...any) (int64, error) {
	db, err := i.makeScopes(i.db.Model(new(Ent)), cds...)
	if err != nil {
		return 0, err
	}

	mapcols := scope.NewColumnValues(cols...)
	if len(mapcols) == 0 {
		return 0, nil
	}
	if userId, ok := db.Get(scope.OperatorKey); ok {
		mapcols = mapcols.Set("UpdatedBy", userId)
	}

	return db.Updates(mapcols.AsMap()).RowsAffected, nil
}

func (i *internalDao[Ent]) makeScopes(db *gorm.DB, cds ...any) (*gorm.DB, error) {
	return makeScopes[Ent](db, cds...)
}

func makeScopes[Ent any](db *gorm.DB, cds ...any) (*gorm.DB, error) {
	if db.Statement != nil && db.Statement.Model != nil {
		db.Statement.Parse(db.Statement.Model)
	}

	return useScopes[Ent](db, false, cds...)
}

func asScopes(cds ...any) ([]func(*gorm.DB) *gorm.DB, error) {
	scopes := make([]func(*gorm.DB) *gorm.DB, 0)

	for _, cdn := range cds {
		if cdn == nil {
			continue
		}
		switch cdn := cdn.(type) {
		case scope.IScope:
			scopes = append(scopes, cdn.MakeScope())
		case scope.Scope:
			scopes = append(scopes, cdn)
		case func(*gorm.DB) *gorm.DB:
			scopes = append(scopes, cdn)
		default:
			subScopes, err := parseScope(cdn)
			if err != nil {
				return nil, err
			}
			scopes = append(scopes, subScopes...)
		}
	}

	return scopes, nil
}

func parseScope(cd any) ([]func(*gorm.DB) *gorm.DB, error) {
	scopes := make([]func(*gorm.DB) *gorm.DB, 0)

	k := reflect.TypeOf(cd)
	v := reflect.ValueOf(cd)
	isPtr := false

	if k.Kind() == reflect.Ptr {
		k = k.Elem()
		v = v.Elem()
		isPtr = true
	}

	switch k.Kind() {
	case reflect.Slice, reflect.Array:
		l := v.Len()
		vals := make([]any, l)
		for i := 0; i < len(vals); i++ {
			vals[i] = v.Index(i).Interface()
		}
		return asScopes(vals)
	case reflect.Struct:
		scopes = append(scopes, func(d *gorm.DB) *gorm.DB {
			if isPtr {
				return d.Where(cd)
			}
			return d.Where(&cd)
		})
	}

	return scopes, nil
}

func useScopes[Ent any](db *gorm.DB, immediately bool, cds ...any) (*gorm.DB, error) {

	for _, cdn := range cds {
		if cdn == nil {
			continue
		}
		switch cdn := cdn.(type) {
		case scope.IScope:
			if cdn == nil {
				continue
			}
			if immediately {
				db = cdn.MakeScope()(db)
			} else {
				db = db.Scopes(cdn.MakeScope())
			}

		case scope.Scope:
			if cdn == nil {
				continue
			}
			if immediately {
				db = cdn(db)
			} else {
				db = db.Scopes(cdn)
			}
		case func(*gorm.DB) *gorm.DB:
			if immediately {
				db = cdn(db)
			} else {
				db = db.Scopes(cdn)
			}
		case Ent:
			db = db.Model(&cdn)
		case *Ent:
			db = db.Model(cdn)
		default:
			return nil, fmt.Errorf("unknown condition type: %T", cdn)
		}
	}

	return db, nil
}
